<?php
/*
 * 数据库操作类   与tp方法相似
 * 有防止sql注入的功能
 * 在控制器controller或者model中使用大D进行实例化数据库对象就可以使用
 * */
header("Content-type: text/html; charset=utf-8");
//require_once __DIR__."/../config.php";
//$GLOBALS["db_config"] = $db_config;
class DB{
    private $pdo;               //数据库
    private $table;             //表名  构造函数中要设置.支持别名形式
    private $field;             //字段  有别名的时候要自带别名
    private $where;             //条件  有别名的时候要自带别名
    private $prepare;           //预编译技术需要使用到的参数   键值对数组
    private $distinct;          //消去重复记录
    private $limit;             //限制  2个参数 开始位置  数量
    private $order;             //排序  默认是升序  可以改为降序desc
    private $sql;               //sql语句   最后一次操作的sql语句,可以检测构造的sql语句是否正确
    private $error;             //错误信息  错误的信息,sql返回的
    private $check_error;       //自动验证的错误
    private $db_config;         //数据库得基本配置
    /*
     * 构造函数
     * 实现自动连接到数据库中
     * */
    public function __construct(){
        require __ROOT__."/config.php";
        $this->db_config = $db_config;
        unset($db_config);      //释放无用资源
        //$dsn = $this->db_config["DB_TYPE"].":host=".$this->db_config["DB_HOST"].":".$this->db_config["DB_PORT"].";dbname=".$this->db_config["DB_NAME"];
        $dsn = $this->db_config["DB_TYPE"].":host=".$this->db_config["DB_HOST"].";dbname=".$this->db_config["DB_NAME"];
        try {
            $this->pdo = new PDO($dsn, $this->db_config["DB_USER"], $this->db_config["DB_PWD"]);
            $this->pdo->query("set names ".$this->db_config["DB_CHARSET"]);         //设置数据库编码问题
            unset($dsn);      //释放无用资源
        }catch (PDOException $e){
            die("<h3>连接数据库失败:".$e->getMessage()."</h3>");
        }
        return $this;
    }

    /*
     * 新增方法
     * $data   新增的数据  是一个键值数组
     * 返回数据   返回false表示有错误  返回数字表示新增数据的id
     * */
    public function add($data=array()){
        $arr_len = count($data);                    //获得数组大小
        if($arr_len > 0 && is_array($data)) {              //整理格式
            $name = "";                 //字段的名称
            $value = "";                //值
            $i = 0;
            foreach ($data as $k => $v) {
                $name .= $k;
                $value .= ":".$k."";               //加入单引号,防止sql注入
                $this->prepare[":".$k] = $v;
                if ($i < $arr_len - 1) {            //还有数据,要添加逗号分割
                    $name .= ",";
                    $value .= ",";
                }
                $i++;
            }
            $this->sql = "insert into " . $this->table . " (".$name.") values (".$value.");";
            unset($name,$value,$i);         //释放无用资源
            $result = $this->pdo->prepare($this->sql);
            if($this->prepare != "") {
                $result->execute($this->prepare);
            } else{
                $result->execute();
            }
            $this->error = $this->pdo->errorInfo();      //整理错误信息
            $this->error = $this->error[2];
            $this->clean();             //清除所有条件field order limit where
            return $this->pdo->lastInsertId();          //返回新增数据的id
        }else{
            die("<h3>传入的参数并非是一个有值的数组!</h3>");
        }
        unset($arr_len,$data);      //释放无用资源
    }

    /*
     * 修改数据
     * $data    要更新的数据    必须要键值数组
     * 修改数据update会结合where使用的
     * 返回数据   false表示修改失败(语句有问题)  返回数字是影响行数
     * */
    public function update($data){
        $arr_len = count($data);                    //获得数组大小
        if($arr_len > 0 && is_array($data)) {              //整理格式
            $update = "";
            $i = 0;
            foreach($data as $k => $v){
                $update .= $k." = :".$k;
                $this->prepare[":".$k] = $v;
                if($i < $arr_len -1){
                    $update .= ",";
                }
                $i++;
            }
            $this->sql = "update " . $this->table . " set " . $update;
            unset($update,$i);         //释放无用资源
            if(trim($this->where) != "") {
                $this->sql .= " where ".$this->where;
            }
            $this->sql .= ";";
            $result = $this->pdo->prepare($this->sql);
            if($this->prepare != "") {
                $result->execute($this->prepare);
            } else{
                $result->execute();
            }
            $this->error = $this->pdo->errorInfo();      //整理错误信息
            $this->error = $this->error[2];
            $this->clean();             //清除所有条件field order limit where  table  prepare
            return $result->rowCount();             //返回影响的行数
        }else{
            die("<h3>传入的参数并非是一个有值的数组!</h3>");
        }
        unset($arr_len,$data);         //释放无用资源
    }

    /*
     * 删除数据
     * 不用传入参数  要配合where使用
     * 如果where为空表示清空表格   如果有条件表示按条件删除
     * 返回  false表示失败(语法有错误)  返回数字表示影响行数
     * */
    public function delete(){
        $this->sql = "delete from ".$this->table;
        if(trim($this->where) != ""){
            $this->sql .= " where ".$this->where;
        }
        $this->sql .= ";";
        $result = $this->pdo->prepare($this->sql);
        if($this->prepare != "") {
            $result->execute($this->prepare);
        } else{
            $result->execute();
        }
        $this->error = $this->pdo->errorInfo();      //整理错误信息
        $this->error = $this->error[2];
        $this->clean();             //清除所有条件field order limit where  table  prepare
        return $result->rowCount();         //返回影响行数
    }

    /*
     * select查询
     * 会自动配合where、order、limit、field使用
     * 不需要传入任何参数就可以使用!
     * 会自动释放资源
     * 返回的是一个二维数组(关联数组),不适用于单一查询
     * */
    public function select(){
        $this->sql = "select ";
        if(trim($this->distinct) != ""){
            $this->sql .= $this->distinct." ";          //消去重复语句
        }
        if(trim($this->field) == ""){
            $this->sql .= "* from ";
        }else{
            $this->sql .= $this->field." from ";
        }
        $this->sql .= $this->table;
        if(trim($this->where) != ""){
            $this->sql .= " where ".$this->where;
        }
        if(trim($this->order) != ""){               //排序
            $this->sql .= " order by ".$this->order;
        }
        if(trim($this->limit) != ""){               //限制
            $this->sql .= " ".$this->limit;
        }
        $this->sql .= ";";
        $result = $this->pdo->prepare($this->sql);
        if($this->prepare != "") {
            $result->execute($this->prepare);
        } else{
            $result->execute();
        }
        $this->error = $this->pdo->errorInfo();      //整理错误信息
        $this->error = $this->error[2];
        $arr = $this->format($result);                  //结果集格式化成关联数组  格式化方法自带清除内容功能
        unset($result);             //释放资源
        $this->clean();             //清除所有条件field order limit where
        if(count($arr) > 0) {
            return $arr[0];
        } else{
            return false;
        }
    }

    /*
     * 获取分页信息  page
     * 和where连用
     * 返回page键值对数组
     * count   总数据量
     * np      当前页数         默认第一页
     * page    总页数
     * num     每一页的数据       默认20条
     * */
    public function page($now_page=1,$num=20){
        $page["count"] = $this->count()+0;
        $page["page"] = ceil($page["count"] / $num)+0;
        if($now_page <= 0){
            $page["np"] = 1+0;
        } else if($now_page > $page["page"]){
            if($page["page"] != 0) {
                $page["np"] = $page["page"]+0;
            } else{
                $page["np"] = 1+0;
            }
        }else{
            $page["np"] = $now_page+0;
        }
        $page["num"] = $num+0;
        return $page;
    }

    /*
     * 单一查询
     * 会自动配合where、order、field使用
     * 不用传入任何参数就可以使用
     * 会自动释放资源
     * 返回一个一维数据(关联数组)
     * */
    public function find(){
        $this->limit(0,1);
        $result = $this->select();
        if(count($result) > 0){
            $result = $result[0];
        }
        return $result;
    }

    /*
     * count函数
     * 获取指定条件的数据的数量
     * 会配合where使用
     * 返回数字表示数量  返回false表示语句有问题
     * */
    public function count(){
        $this->field = "*";
        return $this->sql_methor("count");
    }

    /*
     * avg函数
     * 获取平均数
     * 会自动配置field和where使用  field表示计算平均数的列  where是条件     仅仅支持一个列的求平均值
     * 返回计算的结果 返回false表示语句有问题
     * */
    public function avg(){
        return $this->sql_methor("avg");
    }

    /*
     * min获得字段中的最小值
     * 配合field和where使用 field是列(只能有1个)  where是条件
     * 返回最小值  返回false表示语句有问题
     * */
    public function min(){
        return $this->sql_methor("min");
    }

    /*
     * max获取字段中的最大值
     * 配合field和where使用 field是列(只能有1个)  where是条件
     * 返回最大值  返回false表示语句有问题
     * */
    public function max(){
        return $this->sql_methor("max");
    }

    /*
     * sum求和处理
     * 配合field和where使用 field只能有1个  where是条件
     * 返回求和结果
     * */
    public function sum(){
        return $this->sql_methor("sum");
    }

    /*
     * sql计算函数的封装
     * 参数  传入函数名称就可以 默认是空
     * */
    private function sql_methor($name=""){
        $this->sql = "select ".$name."(".$this->field.") ".$name." from ".$this->table;
        if(trim($this->where) != ""){
            $this->sql .=" where ".$this->where;
        }
        $this->sql .= ";";
        $result = $this->pdo->prepare($this->sql);
        if($this->prepare != "") {
            $result->execute($this->prepare);
        } else{
            $result->execute();
        }
        $this->error = $this->pdo->errorInfo();      //整理错误信息
        $this->error = $this->error[2];
        $arr = $this->format($result);               //结果集格式化成关联数组  格式化方法自带清除内容功能
        unset($result);             //释放资源
        $this->clean();
        return $arr[0][0][$name];
    }

    /*
     * setInc字段数据加处理
     * 配合field和where使用  field只能有1个字段  where条件
     * 参数:
     * 参   加多少 默认1
     * */
    public function setInc($num=1){
        return $this->inc_dec_methor("+",$num);
    }

    /*
     * setDec字段数据加处理
     * 配合field和where使用  field只能有1个字段  where条件
     * 参数:
     * 参   减多少 默认1
     * */
    public function setDec($num=1){
        return $this->inc_dec_methor("-",$num);
    }

    /*
     * 字段加减处理setInc和setDec的方法综合
     * $sign    符号  默认+
     * $num     加减多少 默认1
     * */
    private function inc_dec_methor($sign="+",$num=1){
        $this->sql = "update " . $this->table . " set " . $this->field ." = ".$this->field." ".$sign." ".$num." where ".$this->where;
        $result = $this->pdo->prepare($this->sql);
        if($this->prepare != "") {
            $result->execute($this->prepare);
        } else{
            $result->execute();
        }
        $this->error = $this->pdo->errorInfo();      //整理错误信息
        $this->error = $this->error[2];
        $this->clean();             //清除所有条件field order limit where
        return $result;             //返回影响的行数
    }

    /*
     * 自动验证 check
     * 根据model类所填写的验证的规则进行自动验证(一般在新增数据和修改数据的使用使用本方法)
     * 注意:**自动验证仅仅适合与单表的时候,即table字串只有一个表名而且不带别名的情况下才适用
     * 参数:
     * $data 需要验证的数据(键值数组) 默认是空数组
     * $rule 验证的条件,1,2,3,4,5,6,......默认是1
     * */
    public function check(&$data=array(),$rule=1){
        require_once __ROOT__."/tayphp/Model.class.php";
        $model = new Model(str_replace($this->db_config["DB_PREFIX"],"",$this->table)."Model");
        $result = $model->check($data,$rule);
        $this->check_error = $result["message"];        //记录验证错误信息
        unset($model);          //释放无用资源
        $this->table = "";
        return $result["ispass"];                       //返回true或false
    }

    /*
     * 开启事务管理
     * 开启事务后要就手动的处理结果(上交或回滚)
     * 每一次开启事务只为一次操作做服务,做完一次操作后(即上交或回滚),就会关闭事务管理
     * 可以理解为每一次的commit或者rollback后都要重新开启事务
     * */
    public function startTrans(){
        $this->pdo->beginTransaction();             //开启事务
    }

    /*
     * 提交commit
     * 开启事务后使用才会有效果
     * */
    public function commit(){
        $this->pdo->commit();                       //提交
    }

    /*
     * 回滚处理rollback
     * 开启事务后使用才会有效果的
     * */
    public function rollback(){
        $this->pdo->rollBack();                     //回滚
    }

    /*
     * 使用那一张表   键值对
     * 键 表示   表名
     * 值 表示   别名
     * 支持多表操作的
     * */
    public function table($table=array()){
        $arr_len = count($table);
        if(is_array($table) && $arr_len > 0) {
            $i = 0;
            foreach ($table as $k => $v) {                    //整理数据库表的格式
                if(!empty($v)) {
                    $this->table .= $this->db_config["DB_PREFIX"].$k . " " . $v;              //支持别名模式
                }else{
                    $this->table .= $this->db_config["DB_PREFIX"].$k;                 //防止sql注入
                }
                if($i < $arr_len - 1){
                    $this->table .= ",";
                }
                $i++;
            }
        }
        unset($arr_len,$table);      //释放无用资源
        return $this;
    }

    /*
     * 条件语句必须是一个键值数组
     * 键表示字段的名称
     * 值是一个二维数组,元素1是条件判断符合,元素2是条件的值
     * (对于别名的使用要自带别名)
     * 如果不是数组 就是and   如果是数组 那么第三个元素就是and或者or  如果不是or则是and  空表示and(不区分大小写)
     * 支持between, not between, in, not in操作  不区分大小写
     * 如果键是__string__(不区分大小写)    表示使用手写条件,不会自带关系and或or  要手动写入
     * */
    public function where($where=array()){
        $arr_len = count($where);
        $i = 0;
        foreach($where as $k => $v){
            if(strtolower($k) == "__string__"){                     //如果是自定义条件,优先级最高
                $this->where .= $v." ";
            }
            else if(is_array($v)){                                  //如果是数组就要拆分条件
                $k = explode("|",$k);
                $k_len = count($k);
                if($k_len > 1){
                    $this->where .= "(";
                }
                for($j = 0;$j<$k_len;$j++) {
                    $this->where .= $k[$j] . " " . $v[0];
                    $sign = strtolower($v[0]);
                    $replace = str_replace(".","_",$k[$j]);
                    if ($sign == "between" || $sign == "not between") {       //第二个参数应该是一个数组  元素1是最小  元素2是最大
                        $this->where .= " :".$replace."b1 and :".$replace."b2 ";
                        $this->prepare[":".$replace."b1"] = $v[1][0];
                        $this->prepare[":".$replace."b2"] = $v[1][1];
                    } else if ($sign == "in" || $sign == "not in") {          //第二个参数应该是一个数组  数量不定
                        $v_len = count($v[1]);
                        $this->where .= "(";
                        for($l = 0;$l<$v_len;$l++){
                            $this->where .= ":".$replace."in".$l;
                            $this->prepare[ ":".$replace."in".$l] = $v[1][$l];
                            if($l < $v_len - 1){
                                $this->where .= ",";
                            }
                        }
                        $this->where .= ")";
                        unset($v_len,$l);
                    } else {
                       $this->where .= " :".$replace." ";
                       $this->prepare[":".$replace] = $v[1];
                   }
                   if($j < $k_len -1) {
                    $this->where .= "or ";
                }
            }
            if($k_len > 1) {
                $this->where .= ")";
            }
            unset($k,$k_len,$j,$replace,$sign);
            if($i < $arr_len - 1){
                if(!isset($v[2]) || strtolower($v[2]) != "or") {
                    $this->where .= "and ";
                }else{
                    $this->where .= "or ";
                }
            }
        } else{
                $replace = str_replace(".","_",$k);
                $this->where .= $k." = :".$replace." ";          //非数组默认是and连接
                $this->prepare[":".$replace] = $v;
                if($i < $arr_len - 1){
                    $this->where .= "and ";
                }
                unset($replace);
            }
            $i++;
        }
        $this->where = trim($this->where);          //去除前后无用空格
        unset($arr_len,$where,$sign,$i,$k,$v);         //释放无用资源
        return $this;
    }

    /*
     * 消除重复记录
     * 不用传入任何参数
     * */
    public function distinct(){
        $this->distinct = "distinct";
        return $this;
    }

    /*
     * 字段
     * $field 传入的是一个数组.别名要自带
     * */
    public function field($field=array()){
        $this->field = implode(",", $field);
        return $this;
    }

    /*
     * 排序
     * $order 传入的是一个字符串 内容根据实际情况用户自定义
     * */
    public function order($order){
        $this->order = $order;
        unset($order);
        return $this;
    }

    /*
     * 限制数量
     * $start  从第几条数据开始  默认是0
     * $num    查询的数量       默认是0
     * */
    public function limit($start=0,$num=0){
        $this->limit = "limit ".$start.",".$num;
        unset($start,$num);
        return $this;
    }

    /*
     * 获取sql错误信息
     * */
    public function getError(){
        return $this->error;
    }

    /*
     * 查看sql语句
     * */
    public function getSql(){
        return $this->sql;
    }

    /*
     * 获取自动验证的错误信息
     * */
    public function getCheckError(){
        return $this->check_error;
    }

    /*
     * 清空数据,以免影响到下一次的操作
     * */
    public function clean(){
        $this->order = "";
        $this->field = "";
        $this->limit = "";
        $this->where = "";
        $this->prepare = "";
        $this->table = "";
    }

    /*
     * 结果集合格式化..全部格式为关联数组来进行处理
     * 传入的参数是结果集
     * */
    private function format($result){
        $arr = array();
        $result->setFetchMode(PDO::CASE_NATURAL);           //列明按照原始的方式.
        while($row=$result->fetchAll(PDO::FETCH_ASSOC)){            //获得关联数组
            $arr[] = $row;
        }
        unset($result);     //释放无用资源
        $this->clean();             //清除所有条件field order limit where         结果集自带处理清空功能
        return $arr;
    }

    /*
     * 析构函数
     * 关闭连接
     * */
    public function __destruct(){
        $this->pdo = null;              //释放资源  而且不是长连接  不用做太多处理
    }
}